/**
 * @name Etherpad Reflected File Download CVE-2018-6835
 * @description Returning an unsanitized string in a HTTP response
 *   could cause a Reflected File Download (RFD) vulnerability.
 * @kind path-problem
 * @problem.severity warning
 * @id etherpad/javascript/rfd-cve-2018-6835
 */

import javascript
import semmle.javascript.security.dataflow.ReflectedXss::ReflectedXss
import DataFlow::PathGraph

/**
 * A function with `req` and `res` parameters, and hence most likely an
 * HTTP route handler.
 */
class LikelyRouteHandler extends DataFlow::FunctionNode {
  DataFlow::ParameterNode req;
  DataFlow::ParameterNode res;

  LikelyRouteHandler() {
    req = getParameter(0) and req.getName() = "req" and
    res = getParameter(1) and res.getName() = "res"
  }

  DataFlow::ParameterNode getRequestParameter() {
    result = req
  }

  /** Gets a method of `res` that sends an HTTP response. */
  string getASendMethodName() {
    // res.send
    result = "send"
    or
    // or a method `m` such that there is an assignment `res.m = res.n` where `n`
    // is already known to be a send method
    exists (DataFlow::PropWrite pwn |
      pwn = res.getAPropertyWrite(result) and
      pwn.getRhs() = getASendMethodReference()
    )
  }

  /** Gets a reference to `res.send` or some other known send method. */
  DataFlow::PropRead getASendMethodReference() {
    result = res.getAPropertyRead(getASendMethodName())
  }

  /** Gets a call to the send method. */
  DataFlow::CallNode getASendMethodCall() {
    result = getASendMethodReference().getACall()
  }
}

/** An argument passed to `res.send`, marked as an XSS sink. */
class LikelySendArgument extends Sink {
  LikelySendArgument() {
    this = any(LikelyRouteHandler rh).getASendMethodCall().getAnArgument()
  }
}

/** An access to a request parameter, marked as an XSS source. */
class LikelyRequestParameter extends Source, DataFlow::SourceNode {
  LikelyRequestParameter() {
    exists (DataFlow::SourceNode base | this = base.getAPropertyRead() |
      // either a property access on `req` itself
      base = any(LikelyRouteHandler rh).getRequestParameter()
      or
      // or a more deeply nested property access
      base instanceof LikelyRequestParameter
    )
  }
}

/** A call to `is-var-name`, considered as a sanitizer for untrusted user input. */
class IsVarNameSanitizer extends TaintTracking::AdditionalSanitizerGuardNode, DataFlow::CallNode {
  IsVarNameSanitizer() {
    this = DataFlow::moduleImport("is-var-name").getACall() or
    this = DataFlow::moduleMember("./isValidJSONPName", "check").getACall()
  }

  override predicate appliesTo(TaintTracking::Configuration cfg) {
    any()
  }

  override predicate sanitizes(boolean outcome, Expr e) {
     outcome = true and
     e = getArgument(0).asExpr()
  }
}

// The vulnerability was fixed on 2018-03-23 by adding a call to isValidJSONPName:
//
// https://lgtm.com/projects/g/ether/etherpad-lite/rev/dd7894d3c9389a000d11d3a89962d9fcc9c6c44b
//
// This version of the query adds a sanitizer to exclude those results.
from Configuration xss, DataFlow::PathNode source, DataFlow::PathNode sink
where xss.hasFlowPath(source, sink)
select sink, source, sink, "Reflected File Download (RFD) vulnerability"
